/**
 * 
 */
package edu.cmu.mism.dgjava.jobs;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.progress.IProgressService;

import edu.cmu.mism.dgjava.algorithm.IOptionAlgorithm;
import edu.cmu.mism.dgjava.algorithm.OptionAlgorithmManager;
import edu.cmu.mism.dgjava.data.models.option.AmericanType;
import edu.cmu.mism.dgjava.data.models.option.Derivitives;
import edu.cmu.mism.dgjava.data.models.option.Equity;
import edu.cmu.mism.dgjava.data.models.option.EuropeanType;
import edu.cmu.mism.dgjava.data.models.option.OptionCalculationResult;
import edu.cmu.mism.dgjava.data.models.option.PricingModel;
import edu.cmu.mism.dgjava.data.models.option.impl.DerivitivesImpl;
import edu.cmu.mism.dgjava.data.models.option.impl.PricingModelImpl;
import edu.cmu.mism.dgjava.utils.IDGConstants;
import edu.cmu.mism.dgjava.views.GraphicResults;
import edu.cmu.mism.dgjava.views.OptionDataView;
import edu.cmu.mism.dgjava.views.UnderlyingDataView;

/**
 * The option calculation job implementation that runs and monitors the price
 * calculation process.
 * 
 * @see Job
 * @author Christian
 * 
 */
public class OptionCalculationJob extends Job {
	private Object _underlyingModel;
	private Object _pricingModel;

	private OptionCalculationResult _result;
	private static final int GRAPH_CAL_STEPS = 10;

	public OptionCalculationJob(Object underlyingModel, Object pricingModel) {
		super("Option Price Calculation");
		this._underlyingModel = underlyingModel;
		this._pricingModel = pricingModel;
		this._result = null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @seeorg.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.
	 * IProgressMonitor)
	 */
	public IStatus run(IProgressMonitor monitor) {
		IStatus returnValue = Status.OK_STATUS;
		setPriority(Job.INTERACTIVE);
		monitor.beginTask("Start calculating option prices...", 10);
		try {
			// get the corresponding option algorithm to handle the
			// calculation
			monitor.subTask("Retrieving Option Algorithm...");
			IOptionAlgorithm alg = OptionAlgorithmManager.getInstance()
					.getAlgorithm(_underlyingModel, _pricingModel);
			monitor.worked(1);

			monitor.subTask("Calculating option price..");
			// calculate the option with the given model
			_result = alg.calculate(monitor);
			monitor.worked(8);

			// populate and display the results
			monitor.subTask("Populating results...");
			Display.getDefault().syncExec(new Runnable() {

				@Override
				public void run() {
					populateResult(_result);

				}
			});
			monitor.worked(1);
		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			monitor.done();
		}
		if (monitor.isCanceled())
			returnValue = Status.CANCEL_STATUS;
		return returnValue;
	}

	/**
	 * Populates the result to the result data view
	 * 
	 * @param result
	 *            - the calculation result
	 */
	private void populateResult(OptionCalculationResult result) {
		//populate result in option data view
		IViewPart dataView = PlatformUI.getWorkbench()
				.getActiveWorkbenchWindow().getActivePage().findView(
						OptionDataView.ID);

		((OptionDataView) dataView).populateResult(result);

		//refreshing type view control(enable view tree button if necessary)
		IViewPart typeView = PlatformUI.getWorkbench()
				.getActiveWorkbenchWindow().getActivePage().findView(
						UnderlyingDataView.ID);

		((UnderlyingDataView) typeView).enableShowTreeButton(result.getTree());

		//refreshing graphic view control
		IViewPart graphView = PlatformUI.getWorkbench()
				.getActiveWorkbenchWindow().getActivePage().findView(
						GraphicResults.ID);

		((GraphicResults) graphView).updateDefaultMinMaxValue();
	}

	/**
	 * Calculates the graph result for the given pricing model, the calculation
	 * would be based on the given range for the given x and y attributes
	 * 
	 * @param xAttributeKey
	 * @param minValue
	 *            lower bound set for the x value on the giving x attribute
	 * @param maxValue
	 *            upper bound for the x value on the given x attribute
	 * @param yAttributeKey
	 */
	public void calculateGraphResults(int xAttributeKey, double minValue,
			double maxValue, int yAttributeKey,
			IGraphCalculationCallback callback) {

		IProgressService service = PlatformUI.getWorkbench()
				.getProgressService();
		// instantiate a option graph calculation job to process the
		// calculation

		GraphCalculationJob j = new GraphCalculationJob(
				"Graph Calculation Job", xAttributeKey, minValue, maxValue,
				yAttributeKey, callback);
		service.showInDialog(PlatformUI.getWorkbench()
				.getActiveWorkbenchWindow().getShell(), j);
		j.setRule(new OptionCalculationJobRule());
		j.schedule();

	}

	/**
	 * The graph calculation job used to draw graph from iterative option
	 * calculation
	 * 
	 * @author Christian
	 * 
	 */
	private class GraphCalculationJob extends Job {

		// copied pricing model input
		Object pricingModelCopy;
		Object underlyingTypeModelCopy;

		int xKey;
		int yKey;
		double xMin;
		double xMax;

		IGraphCalculationCallback callback;

		public GraphCalculationJob(String name, int xKey, double xMin,
				double xMax, int yKey, IGraphCalculationCallback callback) {

			super(name);
			// instantiating attributes
			this.xKey = xKey;
			this.yKey = yKey;
			this.xMin = xMin;
			this.xMax = xMax;

			// setting callback instance
			this.callback = callback;

			// copying pricing model
			if (_pricingModel instanceof PricingModel) {
				this.pricingModelCopy = ((PricingModelImpl) _pricingModel)
						.clone();
			}

			// copying underlying derivitive type model
			if (_underlyingModel instanceof Derivitives) {
				this.underlyingTypeModelCopy = ((DerivitivesImpl) _underlyingModel)
						.clone();
			}
		}

		@Override
		protected IStatus run(IProgressMonitor monitor) {
			IStatus returnValue = Status.OK_STATUS;
			setPriority(Job.INTERACTIVE);
			monitor.beginTask("Start calculating graph..", 30);

			final double[] xValues = new double[GRAPH_CAL_STEPS];
			final double[] yValues = new double[GRAPH_CAL_STEPS];
			double interval = (xMax - xMin) / GRAPH_CAL_STEPS;

			try {
				// get the corresponding option algorithm to handle the
				// calculation

				for (int i = 0; i < xValues.length; i++) {
					xValues[i] = xMin + (i * interval);
					monitor.subTask("Adjusting data..." + i);
					adjustXValue(xKey, xValues[i]);

					IOptionAlgorithm alg = OptionAlgorithmManager.getInstance()
							.getAlgorithm(this.underlyingTypeModelCopy,
									this.pricingModelCopy);
					monitor.worked(1);

					monitor.subTask("Calculating value price.." + i);
					// calculate the option with the given model
					OptionCalculationResult result = alg.calculate(monitor);
					yValues[i] = retrieveYValue(yKey, result);
					monitor.worked(2);

				}
				// populate and display the results
				monitor.subTask("Populating results...");
				Display.getDefault().syncExec(new Runnable() {

					@Override
					public void run() {
						callback
								.updateDataSet(
										xValues,
										yValues,
										IDGConstants.OPTION_GRAPHICS_HORIZONTAL_AXIS[xKey],
										IDGConstants.OPTION_GRAPHICS_VERTICAL_AXIS[yKey]);
					}
				});
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				monitor.done();
			}
			if (monitor.isCanceled())
				returnValue = Status.CANCEL_STATUS;
			return returnValue;
		}

		/**
		 * Adjusts the value of underlying model or pricing model input
		 * parameters given the specific x attribute key
		 * 
		 * @see IDGConstants#OPTION_GRAPHICS_HORIZONTAL_AXIS
		 * @param xKey
		 * @param value
		 */
		private void adjustXValue(int xKey, double value) {
			switch (xKey) {
			case 0:
				// Asset Price
				// TODO tianq (need more flexible design for supporting other
				// derivitavies)
				((Equity) this.underlyingTypeModelCopy).setStockPrice(value);
				break;
			case 1:
				// Strike Price
				((PricingModel) this.pricingModelCopy).setExercisePrice(value);
				break;
			case 2:
				// Risk-free Rate
				((Derivitives) this.underlyingTypeModelCopy)
						.setRiskFreeRate(value);
				break;
			case 3:
				// Time to Exercise
				if (this.pricingModelCopy instanceof AmericanType) {
					((AmericanType) pricingModelCopy).setTimeToExpire(value);
				} else if (pricingModelCopy instanceof EuropeanType) {
					((EuropeanType) pricingModelCopy).setTimeToExercise(value);
				}
				break;
			case 4:
				// Volatility
				((Derivitives) underlyingTypeModelCopy).setVolatility(value);
				break;
			default:
				break;
			}
		}

		/**
		 * 
		 * @param result
		 * @return
		 */
		public double retrieveYValue(int yKey, OptionCalculationResult result) {
			double ret = 0.00;
			switch (yKey) {
			case 0:
				// Option Price
				ret = result.getPrice();
				break;
			case 1:
				// Delta
				ret = result.getDelta();
				break;
			case 2:
				// Gamma
				ret = result.getGamma();
				break;
			case 3:
				// Vega
				ret = result.getVega();
				break;
			case 4:
				// Rho
				ret = result.getRho();
				break;
			case 5:
				// Theta
				ret = result.getTheta();
				break;

			default:
				break;

			}
			return ret;
		}
	}

	public Object getPricingModel() {
		return this._pricingModel;
	}

	public Object getUnderlyingTypeMode() {
		return this._underlyingModel;
	}

}
